/*==============================================================================
案例 8：客户流失预测（分类算法）
================================================================================

业务场景：
电信公司面临客户流失问题，需要预测哪些客户可能流失，
以便提前采取挽留措施，降低客户流失率，提高客户生命周期价值。

学习目标：
1. 掌握客户流失预测的完整流程
2. 学习处理不平衡数据集
3. 理解业务导向的模型评估
4. 掌握客户细分和精准营销

数据来源：nlsw88.dta（模拟为客户数据）

作者：Stata ML Course
日期：2025-11-03
==============================================================================*/

clear all
set more off
capture log close

* 设置工作目录
cd "`c(pwd)'"

* 创建输出目录
capture mkdir "output/cases"
capture mkdir "output/cases/figures"
capture mkdir "data/cases"

* 开始日志记录
log using "output/cases/case08_customer_churn.log", replace text

display "=========================================="
display "案例 8：客户流失预测"
display "=========================================="
display ""

/*------------------------------------------------------------------------------
第一部分：数据准备（模拟客户数据）
------------------------------------------------------------------------------*/

display "第一部分：数据准备"
display "------------------"

* 加载数据
sysuse nlsw88, clear

* 删除缺失值
drop if missing(wage) | missing(hours) | missing(ttl_exp) | missing(tenure)

* 创建客户特征
gen customer_id = _n
gen monthly_charge = wage * 10 + runiform() * 50  // 月费用
gen total_charges = monthly_charge * (tenure * 12)  // 总消费
gen contract_length = tenure  // 合约年限
gen service_calls = int(runiform() * 10)  // 客服电话次数
gen complaints = int(runiform() * 5)  // 投诉次数
gen usage_hours = hours * 4  // 月使用时长
gen num_services = int(runiform() * 5) + 1  // 订购服务数量

* 创建客户价值指标
gen avg_monthly_charge = total_charges / (contract_length * 12 + 1)
gen clv = total_charges + monthly_charge * 12  // 客户生命周期价值（简化）
gen service_per_dollar = num_services / monthly_charge  // 性价比
gen complaint_rate = complaints / (contract_length + 1)  // 投诉率

* 创建分类特征
gen long_term_customer = (contract_length >= 3)
gen high_value_customer = (clv > 5000)
gen frequent_caller = (service_calls >= 5)
gen has_complaints = (complaints > 0)
gen premium_user = (monthly_charge > 100)

* 创建目标变量：客户流失（基于多个风险因素）
set seed 20251103

* 标准化特征
foreach var in monthly_charge contract_length service_calls complaints {
    quietly summarize `var'
    gen `var'_std = (`var' - r(mean)) / r(sd)
}

* 创建流失概率
gen churn_score = -0.3 * contract_length_std ///  // 合约短 -> 流失风险高
                  + 0.25 * service_calls_std ///  // 客服电话多 -> 流失风险高
                  + 0.3 * complaints_std ///  // 投诉多 -> 流失风险高
                  + 0.1 * monthly_charge_std ///  // 费用高 -> 流失风险高
                  + runiform() * 0.4

gen churn_prob = 1 / (1 + exp(-churn_score))
gen churn = (churn_prob > 0.65)  // 25% 流失率

label variable churn "客户流失（1=流失，0=留存）"
label variable monthly_charge "月费用（美元）"
label variable total_charges "总消费（美元）"
label variable contract_length "合约年限"
label variable service_calls "客服电话次数"
label variable complaints "投诉次数"
label variable clv "客户生命周期价值"

display "样本量: " _N
display "流失率: " %5.2f (100 * sum(churn) / _N) "%"
display ""

/*------------------------------------------------------------------------------
第二部分：探索性数据分析
------------------------------------------------------------------------------*/

display ""
display "第二部分：探索性数据分析"
display "------------------------"

* 1. 流失分布
tabulate churn

graph bar, over(churn) ///
    title("客户流失分布") ///
    ytitle("客户数量") ///
    blabel(bar, format(%9.0f)) ///
    scheme(s2color)
graph export "output/cases/figures/case08_01_churn_distribution.png", replace

* 2. 流失 vs 合约年限
graph box contract_length, over(churn) ///
    title("合约年限分布（按流失状态）") ///
    ytitle("合约年限（年）") ///
    scheme(s2color)
graph export "output/cases/figures/case08_02_contract_by_churn.png", replace

* 3. 流失 vs 月费用
graph box monthly_charge, over(churn) ///
    title("月费用分布（按流失状态）") ///
    ytitle("月费用（美元）") ///
    scheme(s2color)
graph export "output/cases/figures/case08_03_charge_by_churn.png", replace

* 4. 流失 vs 客服电话
graph box service_calls, over(churn) ///
    title("客服电话次数（按流失状态）") ///
    ytitle("客服电话次数") ///
    scheme(s2color)
graph export "output/cases/figures/case08_04_calls_by_churn.png", replace

* 5. 流失 vs 投诉
graph box complaints, over(churn) ///
    title("投诉次数（按流失状态）") ///
    ytitle("投诉次数") ///
    scheme(s2color)
graph export "output/cases/figures/case08_05_complaints_by_churn.png", replace

* 6. 不同群体的流失率
display ""
display "不同群体的流失率："
display "-------------------"
tabulate long_term_customer, summarize(churn)
tabulate high_value_customer, summarize(churn)
tabulate has_complaints, summarize(churn)

/*------------------------------------------------------------------------------
第三部分：数据分割
------------------------------------------------------------------------------*/

display ""
display "第三部分：数据分割"
display "------------------"

set seed 20251103
gen random = runiform()
gen train = (random <= 0.75)

count if train
display "训练集样本量: " r(N)
count if !train
display "测试集样本量: " r(N)

* 检查流失率
quietly summarize churn if train
display "训练集流失率: " %5.2f (r(mean) * 100) "%"
quietly summarize churn if !train
display "测试集流失率: " %5.2f (r(mean) * 100) "%"

/*------------------------------------------------------------------------------
第四部分：模型 1 - Logistic 回归
------------------------------------------------------------------------------*/

display ""
display "第四部分：Logistic 回归"
display "-----------------------"

logit churn monthly_charge contract_length service_calls complaints ///
    usage_hours num_services married union if train

estimates store logit_model

* 边际效应
display ""
display "边际效应："
margins, dydx(*) atmeans

* 预测
predict prob_logit if !train, pr
gen pred_logit = (prob_logit > 0.5) if !train

/*------------------------------------------------------------------------------
第五部分：模型 2 - Probit 回归
------------------------------------------------------------------------------*/

display ""
display "第五部分：Probit 回归"
display "---------------------"

probit churn monthly_charge contract_length service_calls complaints ///
    usage_hours num_services married union if train

estimates store probit_model

* 预测
predict prob_probit if !train, pr
gen pred_probit = (prob_probit > 0.5) if !train

/*------------------------------------------------------------------------------
第六部分：模型 3 - Lasso Logit（特征选择）
------------------------------------------------------------------------------*/

display ""
display "第六部分：Lasso Logit"
display "---------------------"

lasso logit churn monthly_charge contract_length service_calls complaints ///
    usage_hours num_services total_charges clv service_per_dollar ///
    complaint_rate married union south industry ///
    c.monthly_charge#c.contract_length c.service_calls#c.complaints ///
    if train, selection(cv) rseed(20251103)

* 显示选择的变量
display ""
display "Lasso 选择的变量："
lassocoef, display(coef, standardized)

* 预测
predict prob_lasso if !train, pr
gen pred_lasso = (prob_lasso > 0.5) if !train

/*------------------------------------------------------------------------------
第七部分：模型评估
------------------------------------------------------------------------------*/

display ""
display "第七部分：模型评估"
display "------------------"

* 性能对比
display ""
display "模型性能对比（测试集）"
display "======================"
display ""
display "模型      准确率  精确率  召回率  F1分数  AUC"
display "----------------------------------------------"

foreach model in logit probit lasso {
    * 混淆矩阵指标
    quietly count if churn == 1 & pred_`model' == 1 & !train
    local tp = r(N)
    quietly count if churn == 0 & pred_`model' == 0 & !train
    local tn = r(N)
    quietly count if churn == 0 & pred_`model' == 1 & !train
    local fp = r(N)
    quietly count if churn == 1 & pred_`model' == 0 & !train
    local fn = r(N)
    
    local acc = (`tp' + `tn') / (`tp' + `tn' + `fp' + `fn')
    local prec = `tp' / (`tp' + `fp')
    local rec = `tp' / (`tp' + `fn')
    local f1 = 2 * `prec' * `rec' / (`prec' + `rec')
    
    * AUC（仅对 logit 和 probit）
    if "`model'" == "logit" | "`model'" == "probit" {
        quietly estimates restore `model'_model
        quietly lroc if !train
        local auc = r(area)
    }
    else {
        local auc = .
    }
    
    display "`model'    " %6.3f `acc' "  " %6.3f `prec' "  " %6.3f `rec' "  " %6.3f `f1' "  " %6.4f `auc'
}

* ROC 曲线
quietly estimates restore logit_model
lroc if !train, title("Logistic 回归 ROC 曲线") scheme(s2color)
graph export "output/cases/figures/case08_06_roc_curve.png", replace

/*------------------------------------------------------------------------------
第八部分：客户细分与挽留策略
------------------------------------------------------------------------------*/

display ""
display "第八部分：客户细分与挽留策略"
display "----------------------------"

* 1. 流失风险分层
display ""
display "1. 流失风险分层"
display "----------------"

gen risk_segment = .
replace risk_segment = 1 if prob_logit <= 0.3 & !train  // 低风险
replace risk_segment = 2 if prob_logit > 0.3 & prob_logit <= 0.5 & !train  // 中风险
replace risk_segment = 3 if prob_logit > 0.5 & prob_logit <= 0.7 & !train  // 高风险
replace risk_segment = 4 if prob_logit > 0.7 & !train  // 极高风险

label define risk_seg_lbl 1 "低风险" 2 "中风险" 3 "高风险" 4 "极高风险"
label values risk_segment risk_seg_lbl

tabulate risk_segment if !train

* 各风险层级的实际流失率
display ""
display "各风险层级的实际流失率："
tabulate risk_segment churn if !train, row

* 2. 客户价值 × 流失风险矩阵
display ""
display "2. 客户价值 × 流失风险矩阵"
display "--------------------------"

gen value_risk_segment = ""
replace value_risk_segment = "高价值-高风险" if high_value_customer & risk_segment >= 3 & !train
replace value_risk_segment = "高价值-低风险" if high_value_customer & risk_segment < 3 & !train
replace value_risk_segment = "低价值-高风险" if !high_value_customer & risk_segment >= 3 & !train
replace value_risk_segment = "低价值-低风险" if !high_value_customer & risk_segment < 3 & !train

tabulate value_risk_segment if !train

* 3. 挽留策略建议
display ""
display "3. 挽留策略建议"
display "----------------"

gen retention_strategy = ""
replace retention_strategy = "VIP专属优惠" if value_risk_segment == "高价值-高风险" & !train
replace retention_strategy = "维持现状" if value_risk_segment == "高价值-低风险" & !train
replace retention_strategy = "标准挽留" if value_risk_segment == "低价值-高风险" & !train
replace retention_strategy = "无需干预" if value_risk_segment == "低价值-低风险" & !train

tabulate retention_strategy if !train

* 4. 挽留成本效益分析
display ""
display "4. 挽留成本效益分析"
display "--------------------"

* 假设：
* - 挽留成本：100 美元/客户
* - 挽留成功率：60%
* - 客户年价值：月费用 × 12

gen retention_cost = 100 if !train
gen retention_success_rate = 0.6 if !train
gen annual_value = monthly_charge * 12 if !train
gen expected_benefit = prob_logit * retention_success_rate * annual_value - retention_cost if !train

* 只对预期收益为正的客户进行挽留
gen should_retain = (expected_benefit > 0) if !train

count if should_retain & !train
display "建议挽留的客户数量: " r(N)

quietly summarize expected_benefit if should_retain & !train
display "平均预期收益: " %8.2f r(mean) " 美元"
display "总预期收益: " %10.2f r(sum) " 美元"

/*------------------------------------------------------------------------------
第九部分：关键流失驱动因素
------------------------------------------------------------------------------*/

display ""
display "第九部分：关键流失驱动因素"
display "--------------------------"

quietly estimates restore logit_model

display ""
display "关键发现（基于 Logistic 回归）："
display "--------------------------------"
display "1. 合约年限：每增加 1 年，流失概率降低 " %5.2f (abs(_b[contract_length]) * 100) " 个百分点"
display "2. 客服电话：每增加 1 次，流失概率增加 " %5.2f (_b[service_calls] * 100) " 个百分点"
display "3. 投诉次数：每增加 1 次，流失概率增加 " %5.2f (_b[complaints] * 100) " 个百分点"
display "4. 月费用：每增加 10 美元，流失概率增加 " %5.2f (_b[monthly_charge] * 10 * 100) " 个百分点"

display ""
display "业务建议："
display "----------"
display "1. 鼓励客户签订长期合约（提供折扣）"
display "2. 提升客服质量，减少客户来电需求"
display "3. 建立投诉快速响应机制"
display "4. 优化定价策略，提供性价比套餐"

/*------------------------------------------------------------------------------
第十部分：保存结果
------------------------------------------------------------------------------*/

display ""
display "第十部分：保存结果"
display "------------------"

* 保存预测结果
preserve
keep if !train
keep customer_id churn prob_logit prob_probit prob_lasso risk_segment ///
    value_risk_segment retention_strategy should_retain expected_benefit ///
    monthly_charge contract_length service_calls complaints clv
export delimited using "output/cases/case08_predictions.csv", replace
display "预测结果已保存: output/cases/case08_predictions.csv"
restore

* 保存高风险客户名单
preserve
keep if !train & risk_segment >= 3
keep customer_id prob_logit monthly_charge contract_length service_calls ///
    complaints retention_strategy expected_benefit
sort prob_logit
export delimited using "output/cases/case08_high_risk_customers.csv", replace
display "高风险客户名单已保存: output/cases/case08_high_risk_customers.csv"
restore

/*------------------------------------------------------------------------------
总结
------------------------------------------------------------------------------*/

display ""
display "=========================================="
display "案例 8 完成！"
display "=========================================="
display ""
display "主要成果："
display "1. 建立了 3 个客户流失预测模型"
display "2. 完成客户风险分层和价值细分"
display "3. 提供差异化挽留策略"
display "4. 完成成本效益分析"
display "5. 生成 6 个可视化图表"
display ""
display "关键发现："
display "- 合约年限、客服电话、投诉是流失的主要驱动因素"
display "- 高价值高风险客户需要优先挽留"
display "- 预期总收益: " %10.2f r(sum) " 美元"
display ""
display "输出文件："
display "- 图表: output/cases/figures/case08_*.png (6个)"
display "- 预测: output/cases/case08_predictions.csv"
display "- 高风险客户: output/cases/case08_high_risk_customers.csv"
display "- 日志: output/cases/case08_customer_churn.log"
display ""

log close

